/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright (c) 2022 DilOS
 */

#include <sys/cdefs.h>
#include <sys/queue.h>
#include <sys/types.h>

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <paths.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
 
#include "iconv_impl.h"
#include "citrus_types.h"
#include "citrus_module.h"
#include "citrus_esdb.h"
#include "citrus_hash.h"
#include "citrus_iconv.h"

#define	ISBADF(_h_)     (!(_h_) || (_h_) == (iconv_t)-1)

typedef void	*iconv_t;

static iconv_t
__iconv_open(const char *out, const char *in, struct _citrus_iconv *handle)
{
	/*
	 * Remove anything following a //, as these are options (like
	 * //ignore, //translate, etc) and we just don't handle them.
	 * This is for compatibility with software that uses these
	 * blindly.
	 */
	int ret = _citrus_iconv_open(&handle, in, out);
	if (ret) {
		errno = ret == ENOENT ? EINVAL : ret;
		return ((iconv_t)-1);
	}

	handle->cv_shared->ci_discard_ilseq = strcasestr(out, "//IGNORE");
	handle->cv_shared->ci_ilseq_invalid = false;
	handle->cv_shared->ci_hooks = NULL;

	return ((iconv_t)(void *)handle);
}
 
iconv_t
iconv_open(const char *out, const char *in)
{
	return (__iconv_open(out, in, NULL));
}

int
iconv_close(iconv_t handle)
{
	if (ISBADF(handle)) {
		errno = EBADF;
		return (-1);
	}

	_citrus_iconv_close((struct _citrus_iconv *)(void *)handle);

	return (0);
}

size_t
__iconv(iconv_t handle, char **in, size_t *szin, char **out,
    size_t *szout, uint32_t flags, size_t *invalids)
{
	size_t ret;
	int err;

	if (ISBADF(handle)) {
		errno = EBADF;
		return ((size_t)-1);
	}

	err = _citrus_iconv_convert((struct _citrus_iconv *)(void *)handle,
	    (char **)in, szin, out, szout, flags, &ret);
	if (invalids)
		*invalids = ret;

	if (err) {
		errno = err;
		ret = (size_t)-1;
	}

	return (ret);
}

size_t
iconv(iconv_t handle, const char ** __restrict in, size_t * __restrict szin,
    char ** __restrict out, size_t * __restrict szout)
{
	size_t ret;
	int err;

	if (ISBADF(handle)) {
		errno = EBADF;
		return ((size_t)-1);
	}

	err = _citrus_iconv_convert((struct _citrus_iconv *)(void *)handle,
	    (char **)in, szin, out, szout, 0, &ret);
	if (err) {
		errno = err;
		ret = (size_t)-1;
	}

	return (ret);
}

int
iconvctl(iconv_t handle, int request, void *argument)
{
	struct _citrus_iconv *cv = (struct _citrus_iconv *)(void *)handle;
	struct iconv_hooks *hooks = (struct iconv_hooks *)argument;;
	const char *convname;
	char *dst;
	int *i  = (int *)argument;
	size_t srclen;

	if (ISBADF(handle)) {
		errno = EBADF;
		return (-1);
	}

	switch (request) {
	case ICONV_TRIVIALP:
		convname = cv->cv_shared->ci_convname;
		dst = strchr(convname, '/');
		srclen = dst - convname;
		dst++;
		*i = (srclen == strlen(dst)) && !memcmp(convname, dst, srclen);
		return (0);
	case ICONV_GET_TRANSLITERATE:
		*i = 1;
		return (0);
	case ICONV_SET_TRANSLITERATE:
		return  ((*i == 1) ? 0 : -1);
	case ICONV_GET_DISCARD_ILSEQ:
		*i = cv->cv_shared->ci_discard_ilseq ? 1 : 0;
		return (0);
	case ICONV_SET_DISCARD_ILSEQ:
		cv->cv_shared->ci_discard_ilseq = *i;
		return (0);
	case ICONV_SET_HOOKS:
		cv->cv_shared->ci_hooks = hooks;
		return (0);
	case ICONV_SET_FALLBACKS:
		errno = EOPNOTSUPP;
		return (-1);
	case ICONV_GET_ILSEQ_INVALID:
		*i = cv->cv_shared->ci_ilseq_invalid ? 1 : 0;
		return (0);
	case ICONV_SET_ILSEQ_INVALID:
		cv->cv_shared->ci_ilseq_invalid = *i;
		return (0);
	default:
		errno = EINVAL;
		return (-1);
	}
}

static int
iconvlist_order(const void *first, const void *second)
{
	const char * const *s1 = first;
	const char * const *s2 = second;
 
	return (strcmp(*s1, *s2));
}

void
iconvlist(int (*do_one) (unsigned int, const char * const *, void *), void *data)
{
	char **list, **names;
	size_t sz;
	unsigned int i, j;

	i = 0;
	names = NULL;

	int ret = _citrus_esdb_get_list(&list, &sz, true);
	if (ret) {
		errno = ret;
		list = NULL;
		goto out;
	}
	qsort((void *)list, sz, sizeof(char *), iconvlist_order);
	while (i < sz) {
		j = 0;
		char *slashpos = strchr(list[i], '/');
		names = malloc(sz * sizeof(char *));
		if (names == NULL)
			goto out;
		char *curkey = strndup(list[i], slashpos - list[i]);
		if (curkey == NULL)
			goto out;
		names[j++] = curkey;
		for (; (i < sz) && (memcmp(curkey, list[i], strlen(curkey)) == 0); i++) {
			slashpos = strchr(list[i], '/');
			if (strcmp(curkey, &slashpos[1]) == 0)
				continue;
			char *curitem = strdup(&slashpos[1]);
			if (curitem == NULL)
				goto out;
			names[j++] = curitem;
		}
		do_one(j, (const char * const *)names, data);
		while (j)
			free(names[--j]);
		free(names);
		names = NULL;
	}

out:
	if (names != NULL) {
		while (j)
			free(names[--j]);
		free(names);
	}
	if (list != NULL)
		_citrus_esdb_free_list(list, sz);
}


